#include "colour.hpp"

/** @brief Constructor
    @param r Value for red
    @param g Value for green
    @param b Value for blue
    @param a Value for alpha
*/
util::colour::colour(double r, double g, double b, double a) {
  set(r, g, b, a);
}

/** @brief Copy constructor */
util::colour::colour(const util::colour& c) {
  *this = c;
}

/* @brief Set all the values in the colour
    @param r Value for red
    @param g Value for green
    @param b Value for blue
    @param a Value for alpha
*/
void util::colour::set(double r, double g, double b, double a) {
  data[0] = r;
  data[1] = g;
  data[2] = b;
  data[3] = a;
}

/* @brief Set the red colour component
   @param r The value to set red to
*/
void util::colour::r(double r) {
  data[0] = r;
}

/* @brief Set the green colour component
   @param g The value to set green to
*/
void util::colour::g(double g) {
  data[1] = g;
}

/* @brief Set the blue colour component
   @param b The value to set blue to
*/
void util::colour::b(double b) {
  data[2] = b;
}

/* @brief Set the alpha channel component
   @param a The value to set alpha to
*/
void util::colour::a(double a) {
  data[3] = a;
}

/* @brief Return the red colour component */
double util::colour::r() const {
  return data[0].get_value();
}

/* @brief Return the green colour component */
double util::colour::g() const {
  return data[1].get_value();
}

/* @brief Return the blue colour component */
double util::colour::b() const {
  return data[2].get_value();
}

/* @brief Return the alpha channel component */
double util::colour::a() const {
  return data[3].get_value();
}

/** @brief Return the colour as a c-style array

    This function is provided such that the colour can
    be effieciently passed to c-style libraries, in particular
    any of the glColour4dv() function in OpenGL.

    Note that this function relies on the compiler producing a
    util::colour::d01 type that is the same size as a double.  This is
    a reasonable assumption for the vast majority of compilers, but it
    is not guaranteed by the standard.  If the function starts
    behaving weird, then a size difference could be the culprit.
*/
const double* util::colour::c_data() const {
  return reinterpret_cast<const double*>(data);
}

/** @brief Assignment operator
    @param c The colour to copy
*/
util::colour& util::colour::operator=(const util::colour& c) {
  data[0] = c.data[0];
  data[1] = c.data[1];
  data[2] = c.data[2];
  data[3] = c.data[3];
  return *this;
}

/** @brief Additive assignment operator
    @param c The colour to add
*/
util::colour& util::colour::operator+=(const util::colour& c) {
  data[0] += c.data[0];
  data[1] += c.data[1];
  data[2] += c.data[2];
  data[3] += c.data[3];
  return *this;
}

/** @brief Subtractive assignment operator
    @param c The colour to subtract
*/
util::colour& util::colour::operator-=(const util::colour& c) {
  data[0] -= c.data[0];
  data[1] -= c.data[1];
  data[2] -= c.data[2];
  data[3] -= c.data[3];
  return *this;
}

/** @brief Scalar multiplicative assignment
    @param s The value to multiply by
*/
util::colour& util::colour::operator*=(double s) {
  data[0] *= s;
  data[1] *= s;
  data[2] *= s;
  data[3] *= s;
  return *this;
}

/** @brief Scalar division assignment
    @param s The value to divide by
*/
util::colour& util::colour::operator/=(double s) {
  data[0] /= s;
  data[1] /= s;
  data[2] /= s;
  data[3] /= s;
  return *this;
}

/** @brief Blend to colours
    @param a The first colour to blend
    @param b The second colour to blend
    @param w The weight to blend by

    This function returns a colour that is w * a + (1.0 - w) * b).
    The value of w is constrained to range of zero to one.
*/
util::colour util::blend(const util::colour& a, const util::colour& b, double w) {
  d01 weight = w;
  return a * weight + b * (1.0 - weight);
}

/** @brief Return a colour based on HSV values.
    @param h The h value
    @param s The s value
    @param v The v value

    @todo The h value constraints should be cleaned up with a 
          constrained_value.

    The code is based on the RGB to HSV conversion found in the medit
    program.
 */
util::colour util::fromHSV(double h, double s, double v) {
  util::colour rgb;

  while (h > 360.0) h -= 360.0;
  while (h < 0.0) h += 360.0;

  h /= 60.0;

  int i = int(h);

  double f = h - i;
  double p = v * (1 - s);
  double q = v * (1 - (s * f));
  double t = v * (1 - (s * (1 - f)));

  switch (i) {
  case 0: rgb.r(v); rgb.g(t); rgb.b(p); break;
  case 1: rgb.r(q); rgb.g(v); rgb.b(p); break;
  case 2: rgb.r(p); rgb.g(v); rgb.b(t); break;
  case 3: rgb.r(p); rgb.g(q); rgb.b(v); break;
  case 4: rgb.r(t); rgb.g(p); rgb.b(v); break;
  case 5: rgb.r(v); rgb.g(p); rgb.b(q); break;
  }

  return rgb;
}

/** @brief Add to colours
    @param a The first colour
    @param b The second colour
*/
util::colour operator+(const util::colour& a, const util::colour& b) {
  return util::colour(a.r() + b.r(), a.g() + b.g(), a.b() + b.b(), a.a());
}

/** @brief Subtract to colours
    @param a The first colour
    @param b The second colour
*/
util::colour operator-(const util::colour& a, const util::colour& b) {
  return util::colour(a.r() - b.r(), a.g() - b.g(), a.b() - b.b(), a.a());
}

/** @brief Scalar multiplication (left)
    @param s The scalar component
    @param c The colour to scale
*/
util::colour operator*(double s, const util::colour& c) {
  return util::colour(c.r() * s, c.g() * s, c.b() * s, c.a());
}

/** @brief Scalar multiplication (right)
    @param c The colour to scale
    @param s The scalar component
*/
util::colour operator*(const util::colour& c, double s) {
  return util::colour(c.r() * s, c.g() * s, c.b(), c.a());
}

/** @brief Scalar division (left)
    @param s The scalar component
    @param c The colour to scale
*/
util::colour operator/(double s, const util::colour& c) {
  return util::colour(c.r() / s, c.g() / s, c.b() / s, c.a());
}

/** @brief Scalar division (right)
    @param c The colour to scale
    @param s The scalar component
*/
util::colour operator/(const util::colour& c, double s) {
  return util::colour(c.r() / s, c.g() / s, c.b() / s, c.a());
}

/** @brief Equalitg test
    @param a The first colour
    @param b The second colour
*/
bool operator==(const util::colour& a, const util::colour& b) {
  return
    a.r() == b.r() &&
    a.g() == b.g() &&
    a.b() == b.b() &&
    a.a() == b.a();
}

/** @brief Inequalitg test
    @param a The first colour
    @param b The second colour
*/
bool operator!=(const util::colour& a, const util::colour& b) {
  return
    a.r() != b.r() ||
    a.g() != b.g() ||
    a.b() != b.b() ||
    a.a() != b.a();
}

/** @brief The input streaming operator
    @param is The input stream
    @param c  The colour to stream into

    The text format is:
      r g b a
    where r, g, b and a are doubles and an arbitrary whitespace
    is between each component.
*/
std::istream& operator>>(std::istream& is, util::colour& c) {
  double r = 0.0, g = 0.0, b = 0.0, a = 1.0;
  is >> std::ws >> r >> std::ws >> g >> std::ws >> b >> std::ws >> a;
  c.set(r, g, b, a);
  return is;
}

/** @brief The output streaming operator
    @param os The output stream
    @param c  The colour to stream into

    The tert format is:
      r g b a
    where r, g, b and a are doubles and a single space is
    put between each component.
*/
std::ostream& operator<<(std::ostream& os, const util::colour& c) {
  os << c.r() << " " << c.g() << " " << c.b() << c.a();
  return os;
}
